﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Configuration;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Channels;
using System.ServiceModel.Web;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Caching;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
using Renci.Data.Interop.OpenGIS;
using Renci.Data.Interop.OpenGIS.Exceptions;
using Renci.Ogc.WebService.Common;
using Renci.Ogc.WebService.Configuration;

// The following line sets the default namespace for DataContract serialized typed to be ""
[assembly: ContractNamespace("", ClrNamespace = "Renci.Ogc.WebService")]

namespace Renci.Ogc.WebService
{
    /// <summary>
    /// This class exposes HTTP methods that can be used for OGC Service. 
    /// </summary>
    [System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "http://www.renci.org/ogc", IncludeInSchema = true)]
    [ServiceBehavior(IncludeExceptionDetailInFaults = true, InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple)]
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    [ServiceContract]
    public class Swe
    {
        /// <summary>
        /// Proccesses SWE request using GET method
        /// </summary>
        /// <returns>Response to the request.</returns>
        [WebGet(UriTemplate = "", ResponseFormat = WebMessageFormat.Xml)]
        [OperationContract]
        public Message GetRequest()
        {
            return ProccessRequest(null);
        }

        /// <summary>
        /// Proccesses SWE request using POST method
        /// </summary>
        /// <param name="request">Message with request details</param>
        /// <returns>Response to the request.</returns>
        [WebInvoke(Method = "POST", UriTemplate = "", RequestFormat = WebMessageFormat.Xml, ResponseFormat = WebMessageFormat.Xml)]
        [OperationContract]
        public Message PostRequest(Message request)
        {
            return ProccessRequest(request);
        }

        /// <summary>
        /// Proccesses incoming request.
        /// </summary>
        /// <param name="messageRequest">Message with request details</param>
        /// <returns>Response to the request.</returns>
        private static Message ProccessRequest(Message messageRequest)
        {
            Message resultMessage = null;

            try
            {
                XmlElement xmlRequest = null;
                OperationResult result = null;

                if (messageRequest != null)
                {
                    XmlDocument doc = new XmlDocument();
                    doc.Load(messageRequest.GetReaderAtBodyContents());
                    xmlRequest = doc.DocumentElement;
                }

                NameValueCollection queryParameters = ParseQueryString(WebOperationContext.Current.IncomingRequest.UriTemplateMatch.RequestUri.Query);

                //  Apply global defaults
                if (queryParameters["service"] == null)
                {
                    queryParameters.Add("service", ServiceConfiguration.Settings.DefaultService);
                }
                if (queryParameters["version"] == null)
                {
                    queryParameters.Add("version", ServiceConfiguration.Settings.DefaultVersion);
                }

                var operation = GetServiceOperation(xmlRequest, queryParameters);

                //  Apply operation specific defaults
                foreach (var defaultValue in operation.DefaultValues)
                {
                    if (queryParameters[defaultValue.Name] == null)
                    {
                        queryParameters.Add(defaultValue.Name, defaultValue.DefaultValue);
                    }
                }

                string cacheKey = string.Empty;

                if (operation.CacheEnabled)
                {
                    //  Create cache key to be used to store results in cache
                    cacheKey = WebOperationContext.Current.IncomingRequest.UriTemplateMatch.RequestUri.ToString();

                    if (xmlRequest != null)
                    {
                        cacheKey = xmlRequest.InnerXml;
                    }

                    //  Get cahce resuls if exists
                    result = HttpContext.Current.Cache[cacheKey] as OperationResult;
                }

                if (result == null)
                {
                    //  Get request handler object for selected operation
                    BaseOperation requestHandler = operation.CreateHandlerInstance();

                    SweRequestBase request = null;

                    //  If xmlRequest is null then use query parameters to build an request object
                    if (xmlRequest == null)
                    {
                        request = Activator.CreateInstance(requestHandler.RequestType, new object[] { queryParameters }) as SweRequestBase;
                    }
                    else
                    {
                        XmlSerializer serializer = requestHandler.RequestType.GetSerializer();
                        request = serializer.Deserialize(new StringReader(((XmlElement)xmlRequest).OuterXml)) as SweRequestBase;
                    }

                    request.Validate();

                    //  Hanle request and return results back
                    result = requestHandler.ProcessRequest(request);

                    //  Add result to cache
                    if (operation.CacheEnabled)
                    {
                        HttpContext.Current.Cache.Add(cacheKey, result, null, Cache.NoAbsoluteExpiration, TimeSpan.FromMinutes(operation.CacheTimeout), CacheItemPriority.Normal, null);
                    }
                }

                //  Performs special service operation if specified
                if ((from k in queryParameters.AllKeys
                     where k.StartsWith("$$", StringComparison.OrdinalIgnoreCase)
                     select k).Count() > 0)
                    result = HandleCustomAction(result, queryParameters);

                resultMessage = result.GetResultMessage();
            }
            catch (OgcException exp)
            {
                //  Handle OGC specific errors
                var result = new OperationResult()
                {
                    ResultObject = exp.ExceptionReport,
                };
                resultMessage = result.GetResultMessage();
            }
            catch (System.Exception exp)
            {
                //  Handle all other .NET errors
                var result = new OperationResult()
                {
                    ResultObject = new NoApplicableCodeException("Application error.", exp).ExceptionReport,
                };
                resultMessage = result.GetResultMessage();
            }

            return resultMessage;
        }

        /// <summary>
        /// Gets the service operation.
        /// </summary>
        /// <param name="xmlRequest">The XML request.</param>
        /// <param name="queryParameters">The query parameters.</param>
        /// <returns></returns>
        private static ServiceOperationElement GetServiceOperation(XmlElement xmlRequest, NameValueCollection queryParameters)
        {
            string requestName = queryParameters["request"];
            string serviceName = queryParameters["service"];
            string requestVersion;

            //  Validate "request" parameter
            if (string.IsNullOrEmpty(requestName))
            {
                throw new MissingParameterValueException("request");
            }

            //  Validate "service" parameter
            if (string.IsNullOrEmpty(serviceName))
            {
                throw new MissingParameterValueException("service");
            }

            //  Get version if possible
            if (xmlRequest != null)
            {
                requestVersion = xmlRequest.GetAttribute("version");
            }
            else
            {
                requestVersion = queryParameters["version"];
            }

            //  Get operation configuration or the default one
            var versionOperations = (from o in ServiceConfiguration.Settings.Services
                                     where
                                        o.Operation == requestName
                                        && o.Service == serviceName
                                         && o.Version.ToVersionNumber() == requestVersion.ToVersionNumber()
                                     select o).ToList();

            var defaultOperation = (from o in ServiceConfiguration.Settings.Services
                                    orderby o.Version descending
                                    where
                                       o.Operation == requestName
                                       && o.Service == serviceName
                                    select o).FirstOrDefault();

            //  Make sure there are no multiple configurations for the service
            if (versionOperations.Count() > 1)
            {
                throw new ConfigurationErrorsException(string.Format(CultureInfo.InvariantCulture, "Operation '{1}' of service '{0}' has multiple handlers.", serviceName, requestName));
            }

            //  Get a service configuration
            var operation = versionOperations.FirstOrDefault() ?? defaultOperation;

            //  Make sure service configuration is found
            if (operation == null)
            {
                throw new OperationNotSupportedException(requestName, string.Format(CultureInfo.InvariantCulture, "Operation '{0}' of '{1}' service is not supported.", requestName, serviceName));
            }

            return operation;
        }

        /// <summary>
        /// Handles the custom action specified in URL query path.
        /// </summary>
        /// <param name="result">The result.</param>
        /// <param name="queryParameters">The query parameters.</param>
        /// <returns></returns>
        private static OperationResult HandleCustomAction(OperationResult result, NameValueCollection queryParameters)
        {
            if (queryParameters.AllKeys.Contains("$$validate"))
            {
                Renci.Data.Interop.OpenGIS.Ows.ExceptionReport report = new Renci.Data.Interop.OpenGIS.Ows.ExceptionReport();

                var errors = ValidateXml(result.GetResultMessage());

                var exception = new Renci.Data.Interop.OpenGIS.Ows.ExceptionType
                {
                    ExceptionCode = Renci.Data.Interop.OpenGIS.Ows.ExceptionCode.NoApplicableCode,
                };

                report.Exceptions.Add(exception);

                if (errors.Count > 0)
                {
                    foreach (var error in errors)
                    {
                        exception.ExceptionText.Add(error);
                    }
                }
                else
                {
                    exception.ExceptionText.Add(string.Format(CultureInfo.InvariantCulture, "Result XML has been successfully validated."));
                }

                result = new OperationResult()
                {
                    ResultObject = report,
                };
            }
            return result;
        }

        /// <summary>
        /// Parses the query string using &amp; or ; as parameter seperator character
        /// </summary>
        /// <param name="qstring">The qstring.</param>
        /// <returns></returns>
        private static NameValueCollection ParseQueryString(string qstring)
        {
            NameValueCollection outc = new NameValueCollection();

            Regex r = new Regex(@"[?]?(?<name>[^=&;]+)(=(?<value>[^&;]+))?(&|;)?", RegexOptions.IgnoreCase | RegexOptions.Compiled);

            IEnumerator matches = r.Matches(qstring).GetEnumerator();
            while (matches.MoveNext() && matches.Current != null)
            {
                string name = ((Match)matches.Current).Result("${name}");
                string value = ((Match)matches.Current).Result("${value}");
                value = System.Web.HttpUtility.UrlDecode(value);

                outc.Add(name, value);
            }

            return outc;
        }

        /// <summary>
        /// Holds reference to xmlSettings which used for xml validation
        /// </summary>
        private static XmlReaderSettings _xmlSettings;

        /// <summary>
        /// Validates the XML.
        /// </summary>
        /// <param name="message">The message.</param>
        /// <returns></returns>
        [MethodImpl(MethodImplOptions.Synchronized)]
        private static List<string> ValidateXml(Message message)
        {
            List<string> validationErrors = new List<string>();

            ValidationEventHandler handler = (object sender, ValidationEventArgs e) =>
            {
                validationErrors.Add(string.Format(CultureInfo.InvariantCulture, "{0}:\t{1}", e.Severity.ToString().ToUpper(CultureInfo.InvariantCulture), e.Message));
            };

            if (_xmlSettings == null)
            {
                string resourceBase = string.Format(CultureInfo.InvariantCulture, "{0}.Schemas", typeof(Renci.Ogc.WebService.Swe).Namespace);
                _xmlSettings = new XmlReaderSettings();

                string[] schemas = 
                {
                string.Format(CultureInfo.InvariantCulture, "{0}.ic._2._0.IC-ISM-v2.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.xlink._1._0._0.xlinks.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.gml._3._1._1.base.gml.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.gml._3._1._1.base.dynamicFeature.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.gml._3._1._1.base.topology.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.gml._3._1._1.base.coverage.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.gml._3._1._1.base.coordinateReferenceSystems.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.gml._3._1._1.base.observation.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.gml._3._1._1.base.defaultStyle.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.gml._3._1._1.base.temporalReferenceSystems.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.gml._3._1._1.base.feature.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.gml._3._1._1.base.gmlBase.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.gml._3._1._1.base.geometryBasic0d1d.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.gml._3._1._1.base.geometryBasic2d.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.gml._3._1._1.base.basicTypes.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.gml._3._1._1.base.temporal.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.gml._3._1._1.base.valueObjects.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.gml._3._1._1.base.referenceSystems.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.gml._3._1._1.base.dictionary.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.gml._3._1._1.base.coordinateSystems.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.gml._3._1._1.base.datums.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.gml._3._1._1.base.coordinateOperations.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.gml._3._1._1.base.measures.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.gml._3._1._1.base.dataQuality.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.gml._3._1._1.base.units.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.gml._3._1._1.base.temporalTopology.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.gml._3._1._1.base.direction.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.gml._3._1._1.base.grids.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.gml._3._1._1.base.geometryAggregates.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.gml._3._1._1.base.geometryPrimitives.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.gml._3._1._1.smil.smil20-language.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.gml._3._1._1.smil.smil20.xsd", resourceBase),
                //string.Format(CultureInfo.InvariantCulture, "{0}.gml._3._1._1.smil.xml-mod.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.sweCommon._1._0._1.aggregateTypes.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.sweCommon._1._0._1.basicTypes.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.sweCommon._1._0._1.curveTypes.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.sweCommon._1._0._1.data.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.sweCommon._1._0._1.encoding.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.sweCommon._1._0._1.phenomenon.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.sweCommon._1._0._1.positionTypes.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.sweCommon._1._0._1.simpleTypes.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.sweCommon._1._0._1.temporalAggregates.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.sweCommon._1._0._1.xmlData.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.filter._1._1._0.expr.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.filter._1._1._0.filter.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.filter._1._1._0.filterCapabilities.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.filter._1._1._0.sort.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.ows._1._0._0.ows19115subset.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.ows._1._0._0.owsAll.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.ows._1._0._0.owsCommon.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.ows._1._0._0.owsDataIdentification.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.ows._1._0._0.owsExceptionReport.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.ows._1._0._0.owsGetCapabilities.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.ows._1._0._0.owsOperationsMetadata.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.ows._1._0._0.owsServiceIdentification.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.ows._1._0._0.owsServiceProvider.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.ows._1._1._0.ows19115subset.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.ows._1._1._0.owsAll.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.ows._1._1._0.owsCommon.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.ows._1._1._0.owsContents.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.ows._1._1._0.owsDataIdentification.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.ows._1._1._0.owsDomainType.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.ows._1._1._0.owsExceptionReport.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.ows._1._1._0.owsGetCapabilities.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.ows._1._1._0.owsGetResourceByID.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.ows._1._1._0.owsInputOutputData.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.ows._1._1._0.owsManifest.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.ows._1._1._0.owsOperationsMetadata.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.ows._1._1._0.owsServiceIdentification.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.ows._1._1._0.owsServiceProvider.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.sensorML._1._0._1.base.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.sensorML._1._0._1.process.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.sensorML._1._0._1.method.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.sensorML._1._0._1.sensorML.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.sensorML._1._0._1.system.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.om._1._0._0.observation.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.om._1._0._0.om.xsd", resourceBase),
                //string.Format(CultureInfo.InvariantCulture, "{0}.om._1._0._0.extensions.observationCoverage.xsd", resourceBase),
                //string.Format(CultureInfo.InvariantCulture, "{0}.om._1._0._0.extensions.observationCoverageCompact.xsd", resourceBase),
                //string.Format(CultureInfo.InvariantCulture, "{0}.om._1._0._0.extensions.observationSpecialization_constraint.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.om._1._0._0.extensions.observationSpecialization_override.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.om._1._0._0.extensions.om_extended.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.om._1._0._0.extensions.procedureSpecialization.xsd", resourceBase),
                //string.Format(CultureInfo.InvariantCulture, "{0}.om._1._0._0.extensions.redefineObservation.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.sos._1._0._0.ogc4sos.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.sos._1._0._0.sosAll.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.sos._1._0._0.sosCommon.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.sos._1._0._0.sosContents.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.sos._1._0._0.sosDescribeFeatureType.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.sos._1._0._0.sosDescribeObservationType.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.sos._1._0._0.sosDescribeResultModel.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.sos._1._0._0.sosDescribeSensor.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.sos._1._0._0.sosGetCapabilities.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.sos._1._0._0.sosGetFeatureOfInterest.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.sos._1._0._0.sosGetFeatureOfInterestTime.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.sos._1._0._0.sosGetObservation.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.sos._1._0._0.sosGetObservationById.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.sos._1._0._0.sosGetResult.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.sos._1._0._0.sosInsert.xsd", resourceBase),
                string.Format(CultureInfo.InvariantCulture, "{0}.sos._1._0._0.sosRegisterSensor.xsd", resourceBase),
                };

                foreach (var schema in schemas)
                {
                    _xmlSettings.Schemas.Add(null, XmlReader.Create(Assembly.GetExecutingAssembly().GetManifestResourceStream(schema)));
                }

                _xmlSettings.Schemas.Compile();

                _xmlSettings.ValidationType = ValidationType.Schema;
            }

            _xmlSettings.ValidationEventHandler += handler;

            var reder = XmlReader.Create(message.GetReaderAtBodyContents(), _xmlSettings);

            while (reder.Read()) { }

            _xmlSettings.ValidationEventHandler -= handler;

            return validationErrors;
        }
    }
}